package com.clear.honghefinance.common.service.impl;

import com.clear.honghefinance.common.config.SecurityConfig;
import com.clear.honghefinance.common.constant.CommonConstant;
import com.clear.honghefinance.common.dto.BaseUserInfoDTO;
import com.clear.honghefinance.common.dto.vo.TokenResponse;
import com.clear.honghefinance.common.exception.BizException;
import com.clear.honghefinance.common.exception.LoginException;
import com.clear.honghefinance.common.service.TokenService;
import com.clear.honghefinance.common.util.redis.RedisUtil;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import com.clear.honghefinance.common.util.DateUtil;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@ConditionalOnProperty(prefix = "sys", name = "enable-my-security", havingValue = "true")  // 条件注解
@Service
@Slf4j
@RequiredArgsConstructor
public class TokenServiceImpl<T extends BaseUserInfoDTO> implements TokenService<T> {

    private final SecurityConfig securityConfig;
    private final RedisUtil redisUtil;
    private ThreadLocal<T> userThreadLocal = new ThreadLocal<>();

    /**
     * 设置token/生成token
     *
     * @param userInfo
     */
    @Override
    public T setToken(T userInfo) {
        if (Objects.isNull(userInfo)) {
            throw new LoginException("用户信息不能为空");
        }
        TokenResponse tokenResponse = new TokenResponse();
        // 使用uuid生成token
        tokenResponse.setToken(UUID.randomUUID().toString());
        // 设置过期时间
        tokenResponse.setExpire(securityConfig.getExpire());
        tokenResponse.setExpireDateTime(DateUtil.getExpireTimeStamp(tokenResponse.getExpire(), TimeUnit.DAYS));
        tokenResponse.setTimeUnit(TimeUnit.DAYS);

        String tokenKey = redisUtil.buildKey(CommonConstant.TOKEN_CACHE_KEY, tokenResponse.getToken());
        String cacheToken = redisUtil.get(tokenKey);
        // token重复:理论上可能重复,实际不太可能
        if (Objects.nonNull(cacheToken)) {
            throw new BizException("token已存在，不能登录或注册");
        }

        userInfo.setToken(tokenResponse);
        // 缓存token
        redisUtil.setNx(tokenKey, new Gson().toJson(userInfo),
                Long.valueOf(userInfo.getToken().getExpire() + ""), TimeUnit.SECONDS);

        return userInfo;
    }

    /**
     * 1、检验token
     * 2、根据规则判断是否要刷新token
     *
     * @param token
     * @return
     */
    @Override
    public T checkToken(String token) {
        if (Strings.isBlank(token)) {
            throw new BizException("token非法");
        }
        String userInfoCache = redisUtil.get(redisUtil.buildKey(CommonConstant.TOKEN_CACHE_KEY, token));
        if (Strings.isBlank(userInfoCache)) {
            throw new BizException("token不存在或已过期");
        }
        // 反序列化
        T userInfo = new Gson().fromJson(userInfoCache, new TypeToken<BaseUserInfoDTO>() {
        }.getType());
        if (Objects.isNull(userInfo)) {
            throw new BizException("token不存在或已过期");
        }
        // 判断是否需要刷新token
        boolean isRefresh = isRefreshToken(userInfo.getToken());
        if (isRefresh) {
            userInfo.getToken().setExpireDateTime(DateUtil.getExpireTimeStamp(userInfo.getToken().getExpire(), TimeUnit.DAYS));

            redisUtil.setNx(userInfoCache, new Gson().toJson(userInfo),
                    Long.valueOf(userInfo.getToken().getExpire() + ""), TimeUnit.SECONDS);
        }
        return userInfo;
    }

    /**
     * 是否刷新token（用于操作接口时，是否给token续命）
     *
     * @param tokenResponse
     * @return
     */
    @Override
    public boolean isRefreshToken(TokenResponse tokenResponse) {
        if (Objects.isNull(tokenResponse)) {
            throw new BizException("token无效或已过期");
        }
        //求token还剩多久过期
        long expireTime = tokenResponse.getExpireDateTime() - DateUtil.getSecondTimeStamp();
        if (expireTime <= 0) {
            throw new BizException("token过期");
        }

        //求token已经使用了多长时间
        long useTime = tokenResponse.getExpire() - expireTime;
        //token使用时间超过三分之一则刷新
        if (useTime > (tokenResponse.getExpire() / 3)) {
            return true;
        }
        return false;
    }

    /**
     * 获取用户信息
     *
     * @return
     */
    @Override
    public T getThreadLocalUser() {
        return Objects.isNull(userThreadLocal) ? null : userThreadLocal.get();
    }

    /**
     * 设置用户信息
     *
     * @param userInfo 用户信息
     */
    @Override
    public void setThreadLocalUser(T userInfo) {
        if (Objects.isNull(userThreadLocal)) {
            userThreadLocal = new ThreadLocal<>();
        }
        userThreadLocal.set(userInfo);
    }

    /**
     * 删除本地用户
     */
    @Override
    public void removeThreadLocalUser() {
        if (Objects.nonNull(userThreadLocal)) {
            userThreadLocal.remove();
        }
    }

    /**
     * 获取用户id
     *
     * @return 用户id
     */
    @Override
    public Long getThreadLocalUserId() {
        T user = getThreadLocalUser();
        if (Objects.nonNull(user) && user.getId() != null && user.getId() > 0) {
            return user.getId();
        }
        throw new LoginException("获取用户id异常");
    }

    /**
     * 获取租户id
     *
     * @return
     */
    @Override
    public Long getThreadLocalTenantId() {
        T user = getThreadLocalUser();
        if (Objects.nonNull(user) && user.getTenantId() != null && user.getTenantId() > 0) {
            return user.getTenantId();
        }
        throw new LoginException("获取租户id异常");
    }

    /**
     * 清除token
     *
     * @return
     */
    @Override
    public void clearToken() {
        T user = getThreadLocalUser();
        try {
            if (user == null || user.getToken() == null || StringUtils.isBlank(user.getToken().getToken())) {
                return;
            }
            String key = CommonConstant.TOKEN_CACHE_KEY + user.getToken().getToken();
            redisUtil.del(key);
            log.info("删除token：{}成功，用户id：{}", user.getToken().getToken(), user.getId());
        } catch (Exception e) {
            throw new BizException("清除token异常");
        }
    }

}
